package org.hackystat.devcathlon.engine.event;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;

import org.hackystat.devcathlon.engine.db.util.HibernateUtil;
import org.hackystat.devcathlon.engine.match.Match;
import org.hackystat.devcathlon.ui.EventTask;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 * A thread-safe singleton class that manages access to the Events in this system.
 * 
 * @author John Ly
 */
public class EventManager {
  /** The singleton instance of this class. */
  private static EventManager theInstance = new EventManager();
  /** The (event name, event instance) tuples. */
  private Map<String, Event> names2events = new HashMap<String, Event>();
  /** Holds the HackystatLogger for this Service. */
  private Logger logger;

  /** The private constructor. */
  private EventManager() {
    this.logger = HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon");
  }

  /**
   * Returns the singleton instance of the EventManager.
   * 
   * @return The EventManager.
   */
  public static EventManager getInstance() {
    return EventManager.theInstance;
  }

  /**
   * Adds an event to the system.
   * 
   * @param name The name of the event. Must be unique.
   * @param event The event instance.
   * @return The Event.
   * @throws EventException If event name is not unique.
   * @throws Exception If error creating and returning a new instance of the Event.
   */
  public synchronized Event addEvent(Event event) throws EventException, Exception {
//    if (this.names2events.containsKey(name)) {
//      throw new EventException("Attempt to add event with duplicate name: " + name);
//    }
    Event theEvent = getEvent(event.getName());
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      if (null == theEvent) {
        session.beginTransaction();
        this.names2events.put(event.getName(), event);
        session.save(event);
        session.getTransaction().commit();
      }
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return event;
  }

  /**
   * Returns the Event instance associated with the name, or null if not found.
   * 
   * @param name The name of the Event to be retrieved.
   * @return The Event instance, or null if not found.
   */
  public synchronized Event getEvent(String name) {
//    return this.names2events.get(name);
    Event event = null;
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    String hqlSelect = "from Event e where e.name = :name";
    try {
      session.beginTransaction();
      Query query = session.createQuery(hqlSelect).setString("name", name);
      query.setMaxResults(1);
      event = (Event) query.uniqueResult();
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return event;
  }

  /**
   * Returns the Event instance associated with the name, or null if not found.
   * 
   * @param match The match the events are running for.
   * 
   * @param events The list of events for which tasks will be created.
   */
  public synchronized void createEventTasks(Match match, List<Event> events) {
    if (match.allInvitedTeamsReady()) {
      try {
        if (Tstamp.makeTimestamp().compare(match.getEndDate()) < 0) {
          for (Event event : events) {
            Timer timer = new Timer();
            Date date;
            XMLGregorianCalendar timestamp = Tstamp.makeTimestamp();
            if (timestamp.compare(match.getStartDate()) < 0) {
              date = match.getStartDate().toGregorianCalendar().getTime();
            }
            else {
              date = timestamp.toGregorianCalendar().getTime();
            }
            logger.info("Creating Event task");
            timer.scheduleAtFixedRate(new EventTask(match, event), date, (long) event
                .getWakeupInterval() * 60000);
            match.addTimer(event, timer);
          }
        }
      }
      catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }
}
